﻿using Microsoft.AspNet.Http;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.OptionsModel;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.WebApi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Sino.CapacityCloud.Services
{

    public class ApplicationUserManager
        : UserManager<ApplicationUser>
    {

        public override bool SupportsUserLockout
        {
            get
            {
                return true;
            }
        }

        public override bool SupportsUserClaim
        {
            get
            {
                return true;
            }
        }

        public override bool SupportsUserRole
        {
            get
            {
                return true;
            }
        }

        public ApplicationUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators, IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger, IHttpContextAccessor contextAccessor)
            : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, contextAccessor)
        {
        }

        public async Task<ApplicationUser> CreateUserAsync(string userName, string email, string phoneNumber, string[] roleNames, string password = null)
        {
            var user = await this.FindByNameAsync(userName);
            if (user == null)
            {
                user = new ApplicationUser()
                {
                    UserName = userName,
                    Email = email,
                    PhoneNumber = phoneNumber,
                    CertificatePhotoCertificationState = CertificationStatus.Uncommitted,
                    DrivingLicenseKeyCertificationState = CertificationStatus.Uncommitted,
                    IdentityCardCertificationState = CertificationStatus.Uncommitted
                };
                var identityResult = await this.CreateAsync(user);
                if (identityResult.Succeeded)
                {
                    foreach (var roleName in roleNames)
                    {
                        identityResult = await this.AddToRoleAsync(user, roleName);
                        if (!identityResult.Succeeded)
                        {
                            this.Logger.LogError($"Failed to add the user {user.Id} to role {roleName}.");
                        }
                        await this.AddClaimAsync(user, new Claim(ApplicationClaim.Manage, ApplicationClaim.Allowed));
                        await this.AddClaimAsync(user, new Claim(ApplicationClaim.WebAPI, ApplicationClaim.Allowed));
                        await this.UpdateAsync(user);
                        return user;
                    }
                }
                else
                {
                    this.Logger.LogError($"Failed to create the Administrator user. {string.Join("\n", identityResult.ToErrorStrings())}");
                }
            }
            return null;
        }

        public async Task<ApplicationUser> FindByTokenAsync(string token)
        {
            return await this.Users
                .AsNoTracking()
                .FirstOrDefaultAsync(x =>
                    x.Token == token);
        }

        public async Task<ApplicationUser> CreateUserAsync(UserDetails info)
        {
            try
            {
                info.PhoneNumber = info.PhoneNumber?.Trim();
                if (string.IsNullOrWhiteSpace(info.PhoneNumber))
                {
                    throw new ArgumentNullException("PhoneNumber");
                }
                IdentityResult identityResult = null;
                var user = await this.FindByNameAsync(info.PhoneNumber);
                if (user == null)
                {
                    // If the user is not found, then we should register him or her fist.
                    user = new ApplicationUser()
                    {
                        PhoneNumber = info.PhoneNumber,
                        UserName = info.PhoneNumber,
                        RealName = info.RealName,
                        CertificatePhotoKey = info.CertificateImageKey,
                        AvatarKey = info.AvatarUri,
                        CarriageType = info.CarriageType,
                        DrivingLicenseKey = info.DrivingLicenseImageKey,
                        IdentityCardKey = info.IdentityCardImageKey,
                        CertificatePhotoCertificationState = (CertificationStatus)info.RealNameState,
                        DrivingLicenseKeyCertificationState = (CertificationStatus)info.RealNameState,
                        IdentityCardCertificationState = (CertificationStatus)info.RealNameState
                    };
                    identityResult = await this.CreateAsync(user);
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to set role to the user {info.PhoneNumber} with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                    }
                    identityResult = await this.AddToRoleAsync(user, ApplicationRole.Driver);
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to add user {user.Id} to '{ApplicationRole.Driver}' role with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                    }
                    await this.UpdateAsync(user);
                }

                return user;
            }
            catch (Exception ex)
            {
                this.Logger.LogError(ex.Message, ex);
            }

            return null;
        }

        public async Task<ApplicationUser> AddOrUpdateUserInfoAsync(string driverPhone, string driverName, string carId, string domainName, string[] roleNames = null)
        {
            try
            {
                if (string.IsNullOrEmpty(driverPhone))
                {
                    throw new ArgumentNullException("driverPhone");
                }
                var user = this.Users.FirstOrDefault(x => x.PhoneNumber == driverPhone);
                if (user == null)
                {
                    user = await this.CreateUserAsync(driverPhone, driverName + $"@{domainName}", driverPhone, roleNames ?? new string[]
                    {
                        ApplicationRole.Driver
                    });
                    if (user == null)
                    {
                        this.Logger.LogError($"Failed to add user driverName={driverName}, driverPhone={driverPhone}.");
                    }
                    user.CarId = carId;
                }
                else
                {
                    user.RealName = driverName;
                }
                await this.UpdateAsync(user);
                return user;
            }
            catch (Exception ex)
            {
                this.Logger.LogError("Failed to add or update a user from EventBus.", ex);
                return null;
            }
        }

    }

}